﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace ExcelDna.Integration.Rtd
{
    internal class AsyncObservableState
    {
        readonly Guid _id;
        readonly AsyncCallInfo _callInfo; // Bit ugly having this here - need a bi-directional dictionary...
        readonly IExcelObservable _observable;
        ExcelRtdObserver _currentObserver;
        IDisposable _currentSubscription;

        public AsyncObservableState(Guid id, AsyncCallInfo callInfo, IExcelObservable observable)
        {
            _id = id;
            _callInfo = callInfo;
            _observable = observable;
        }

        public object GetValue()
        {
            if (_currentObserver == null || !_currentObserver.IsCompleted)
            {
                // NOTE: At this post the SynchronizationManager must be registered!
                if (!SynchronizationManager.IsInstalled)
                {
                    Debug.Print("SynchronizationManager not registered!");
                    throw new InvalidOperationException("SynchronizationManager must be registered for async and observable support");
                }

                // Ensure that Excel-DNA knows about the RTD Server, since it would not have been registered when loading
                ExcelObserverRtdServer.EnsureRtdServerRegistered();

                // Refresh RTD call
                // NOTE: First time this will result in a call to ConnectData, which will call Subscribe and set the _currentObserver
                object unused = XlCall.RTD("ExcelDna.Integration.Rtd.ExcelObserverRtdServer", null, _id.ToString());
            }

            // No assumptions about previous state here - could have re-entered this class.

            // TODO: Allow customize this value?
            //       Not too serious since the user can remap in the UDF.
            if (_currentObserver == null) return ExcelError.ExcelErrorNA;

            // Subsequent calls get value from Observer
            return _currentObserver.Value;
        }

        public void Subscribe(ExcelRtdObserver rtdObserver)
        {
            _currentObserver = rtdObserver;
            _currentSubscription = _observable.Subscribe(rtdObserver);
        }

        public void Unsubscribe()
        {
            Debug.Assert(_currentSubscription != null);
            _currentSubscription.Dispose();
            _currentSubscription = null;
            _currentObserver = null;
        }

        public AsyncCallInfo GetCallInfo()
        {
            return _callInfo;
        }
    }
}
